Using Server-Sent-Events (SSE) in .NET
Sample Project
Executable sample for this article: CloudyWing/SseProgressSample.
The sample was written at a different time than this article and has been rewritten using .NET 10. It uses Response.ContentType to set headers, handles client disconnection with CancellationToken, and includes progress reporting for long-running tasks. There are differences from the early implementation in the text, so it is recommended to read both for comparison.
This article mainly introduces how to implement real-time communication using SSE in .NET.
Comparison of SSE and Other Real-time Communication Methods
In JavaScript, there are three common ways to implement real-time communication:
Polling: The client sends requests to the server periodically to request and receive data. This approach often leads to excessive requests and responses, consuming significant resources.
Server-Sent-Events (SSE): A unidirectional connection where the server pushes updated data to the client. This method has the following advantages: unidirectional connection, reduced server burden, and only one TCP connection, which reduces bandwidth waste.
WebSocket: Establishes a bidirectional connection between the client and the server, allowing both parties to send data at any time. WebSocket can reduce network traffic and improve efficiency.
SSE JavaScript Implementation
SSE has three default events: open, error, and message. Below is a simple JavaScript example:
SSE Code Example
const sse = new EventSource('Your API Url');
// Listen for the open event, which triggers when the connection is successful
sse.addEventListener('open', function (e) {
console.log('SSE connection opened');
});
// Listen for the error event, which triggers when a connection error occurs
sse.addEventListener('error', function (e) {
console.log('SSE connection error');
});
// Listen for the message event, which triggers when a message is received
sse.addEventListener('message', function (e) {
console.log('SSE message received', e);
const data = JSON.parse(e.data);
const messageElement = document.createElement('div');
messageElement.textContent = data.message;
document.body.appendChild(messageElement);
});
// Listen for the custom end event
sse.addEventListener('end', function (e) {
console.log('SSE custom end', e);
sse.close();
});withCredentials Property
When creating a Server-Sent Events (SSE) object, you can use the withCredentials property to specify whether to send CORS authentication information when making cross-origin requests. This property has no effect in same-origin scenarios.
WARNING
When SSE uses cross-origin requests, the server-side headers also need to be configured for CORS accordingly.
const sse = new EventSource('Your API Url', { withCredentials: true } );Implementing an SSE Server in .NET
- The server sends a message containing an event and data to the client, and the client can use the EventSource object to receive these messages. Both event and data are optional, and each message ends with an empty line.
- The Content-Type returned by the SSE server is
text/event-stream.
Implementation using ASHX (Generic Handler)
ASHX is a feature of ASP.NET Web Forms that can easily handle web requests. In SSE applications, you can use ASHX to handle SSE requests. Below is a simple ASHX code example:
public class SseHandler : IHttpHandler {
public void ProcessRequest (HttpContext context) {
// Set the response Content-Type to text/event-stream
context.Response.ContentType = "text/event-stream";
context.Response.CacheControl = "no-cache";
// Simulate an SSE event stream, sending a message every second
int count = 0;
while (count < 10) {
count++;
context.Response.Write("data: " + "{\"message\": \"Hello SSE " + count + "\"}\n\n");
context.Response.Flush();
System.Threading.Thread.Sleep(1000);
}
// Return a custom end event; the JavaScript example above will Close SSE here
context.Response.Write("event: end\ndata: {}\n\n");
context.Response.Flush();
context.Response.End();
}
public bool IsReusable {
get {
return false;
}
}
}TIP
Each message ends with an empty line, so the first \n indicates a line break in the message, and the second \n indicates the end of the message.
Implementation using ASP.NET Core Web API
[ApiController]
[Route("[controller]")]
public class SseController : ControllerBase {
[HttpGet]
public async Task GetAsync() {
// Set the response Content-Type to text/event-stream
Response.Headers.Add("Content-Type", "text/event-stream;");
Response.Headers.Add("Cache-Control", "no-cache");
// Simulate an SSE event stream, sending a message every second
int count = 0;
while (count < 10) {
count++;
await Response.WriteAsync($"data: " + "{\"message\": \"Hello SSE " + count + "\"}\n\n");
await Response.Body.FlushAsync();
await Task.Delay(TimeSpan.FromSeconds(1));
}
// Return a custom end event; the JavaScript example above will Close SSE here
await Response.WriteAsync("event: end\ndata: {}\n\n");
await Response.Body.FlushAsync();
}
}Changelog
- 2023-03-15 Initial version created.
- 2024-02-17 Corrected content related to withCredentials.
- 2026-05-17 Added link to the GitHub sample project.